use std::fs::File;
use std::io::Read;
use std::io::Seek;
use std::io::Write;
use std::io::BufReader;
use std::io::BufWriter;
use std::io::ErrorKind;

use std::path::Path;

use util::RawData;

use crate::constants::BACKUP_FILE_RECIPE_LOG;
use crate::constants::RESTORE_FILE_RECIPE_LOG;

use super::RecipeError;
use super::RecipeResult;
use super::constants::FILE_RECORD_BUF_LEN;

#[derive(PartialEq,Debug,RawData)]
struct FileFixedInfo {
	pub num:u64,
	pub size:u64,
	pub len:usize,
}

impl FileFixedInfo {
	fn empty() -> Self {
		Self {
			num:0,
			size:0,
			len:0,
		}
	}
}

#[derive(PartialEq,Debug)]
pub struct FileInfo {
	info:FileFixedInfo,
	name:String,
}

impl FileInfo {
	pub fn new(num:u64,size:u64,name:String) -> Self {
		Self {
			info:FileFixedInfo {
				num:num,
				size:size,
				len:name.len(),
			},
			name:name,
		}
	}
	
	fn with_info(info:FileFixedInfo,name:String) -> Self {
		assert_eq!(info.len,name.len());
		Self {
			info:info,
			name:name,
		}
	}
	
	pub fn num(&self) -> u64 {
		self.info.num
	}
	 
	pub fn size(&self) -> u64 {
		self.info.size
	}
	
	pub fn name(&self) -> &String {
		&self.name
	}
}

pub struct FileRecipeWriter {
	output:BufWriter<File>,
}

impl FileRecipeWriter {
	pub fn from_path<P:AsRef<Path>+?Sized>(path:&P) -> RecipeResult<Self> {
		Ok(Self {
			output:BufWriter::with_capacity(
				FILE_RECORD_BUF_LEN,
				File::create(path).map_err(|e|RecipeError::FileWriterOpen(e))?,
			),
		})
	}
	
	pub fn from_file(f:File) -> Self {
		Self {
			output:BufWriter::with_capacity(FILE_RECORD_BUF_LEN,f),
		}
	}

	pub fn cursor(&mut self) -> std::io::Result<u64> {
		self.output.stream_position()
	}
	
	pub fn append(&mut self,f:&FileInfo) -> RecipeResult<()> {
		log::debug!(target:BACKUP_FILE_RECIPE_LOG,"file recipe is written with {:?} at 0x{:0x}\n",f,self.cursor().unwrap());
		self.output.write(f.info.as_raw_data()).map_err(|e|RecipeError::AppendFile(f.name().clone(), e))?;
		self.output.write(f.name().as_bytes()).map_err(|e|RecipeError::AppendFile(f.name().clone(), e))?;
		Ok(())
	}
}



pub struct FileRecipeReader {
	input:BufReader<File>,
}

impl FileRecipeReader {
	pub fn from_path<P:AsRef<Path>+?Sized>(path:&P) -> RecipeResult<Self> {
		Ok(Self {
			input:BufReader::with_capacity(
				FILE_RECORD_BUF_LEN,
				File::open(path).map_err(|e|RecipeError::FileReaderOpen(e))?,
			),
		})
	}
	
	pub fn from_file(f:File) -> Self {
		Self {
			input:BufReader::with_capacity(FILE_RECORD_BUF_LEN,f),
		}
	}

	pub fn cursor(&mut self) -> std::io::Result<u64> {
		self.input.stream_position()
	}

	pub fn get(&mut self) -> RecipeResult<Option<Box<FileInfo>>> {
		let read_position = self.cursor().unwrap();
		let mut info = FileFixedInfo::empty();
		match self.input.read_exact(info.as_raw_mut()) {
			Ok(()) => {}
			Err(e) => {
				return match e.kind() {
					ErrorKind::UnexpectedEof => { Ok(None) }
					_ => { Err(RecipeError::GetFile(e)) } 
				};
			}
		}
		let mut name = Vec::with_capacity(info.len);
		unsafe { name.set_len(info.len); }
		match self.input.read_exact(&mut name[..])  {
			Ok(()) => {}
			Err(e) => {
				return match e.kind() {
					ErrorKind::UnexpectedEof => { Ok(None) }
					_ => { Err(RecipeError::GetFile(e)) } 
				};
			}
		}
		let name = String::from_utf8(name);
		match name {
			Ok(name) => {
				let info = Box::new(FileInfo::with_info(info,name));
				log::debug!(target:RESTORE_FILE_RECIPE_LOG,"file recipe is read with {:?} at 0x{:0x}\n",info,read_position);
				Ok(Some(info))
			}
			Err(e) => {
				Err(RecipeError::GetFileName(e))
			}
		}
	}
}
